package pers.jing.android.helper

import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import com.android.prefs.AndroidLocationsSingleton
import com.android.sdklib.repository.AndroidSdkHandler
import com.android.sdklib.repository.LoggerProgressIndicatorWrapper
import com.android.sdklib.repository.legacy.LegacyDownloader
import com.android.utils.StdLogger
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.jetbrains.skia.Image
import org.slf4j.LoggerFactory
import pers.jing.android.cmd.tools.InstallAction
import pers.jing.android.cmd.tools.SdkManagerSettings
import pers.jing.android.helper.CmdTools.executeCommand
import pers.jing.android.helper.model.CODE_SUCCESS
import pers.jing.android.helper.model.CommandResult
import pers.jing.android.helper.model.DeviceInfo
import pers.jing.android.helper.model.PackageInfo
import pers.jing.android.helper.model.UiHierarchy
import pers.jing.android.helper.model.UiNode
import java.io.File
import java.io.FileInputStream
import java.nio.file.FileSystems

/**
 * Android Cmdline的常用命令
 * 端口号设置, 必须在adb后面, adb -P 5037 start-server
 */
object AndroidSdkTools {

    private val logger = LoggerFactory.getLogger("AndroidSdkTools")

    /**
     * 检测ADB是否存在
     */
    fun checkAdbExists(androidSdkPath: String): Boolean {
        var platformToolFolder = File(androidSdkPath, PACKAGE_PLATFORM_TOOLS)
        var adbFile = File(platformToolFolder, getAdbName())
        return adbFile.exists()
    }

    /**
     * --------------
     * adb.exe --version
     * Android Debug Bridge version 1.0.41
     * Version 35.0.2-12147458
     * Installed as C:\Android\Sdk\platform-tools\adb.exe
     * Running on Windows 10.0.22610
     * --------------
     */
    fun getAdbVersion(androidSdkPath: String): String {
        try {
            val process = ProcessBuilder()
                .command("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}", "version")
                .start()
            process.inputReader().use {
                var version = it.readLine()
                //读取第一个
                if (version != null && version.isNotBlank()) {
                    version = version.replace("Android Debug Bridge version ", "")
                    return version
                }
            }
            return ""
        } catch (ignored: Exception) {
            return ""
        }
    }

    fun getAdbName(): String {
        val osName = System.getProperty("os.name")
        if (osName.contains("Windows", true)) {
            return "adb.exe"
        }
        return "adb"
    }

    /**
     * 安装platform-tools, 其中包含adb
     */
    @Throws
    fun installPlatformTools(cmdToolsPath: String) {
        val settings = SdkManagerSettings()
        val downloader = LegacyDownloader(settings)
        val fileSystem = FileSystems.getDefault()
        val path = fileSystem.getPath(cmdToolsPath)
        val androidSdkHandler = AndroidSdkHandler.getInstance(AndroidLocationsSingleton, path)
        val progressIndicator = LoggerProgressIndicatorWrapper(StdLogger(StdLogger.Level.VERBOSE))
        val installAction =
            InstallAction(settings, androidSdkHandler, downloader, progressIndicator, arrayOf("platform-tools"))
        installAction.execute()
    }


    /**
     * 获取已连接设备的序列ID, 用于访问指定设备
     * 如果使用无线调试连接, 如果断开连接, adb devices看到的信息如下:
     * adb-AM3S6R3407002391-fjL5sH._adb-tls-connect._tcp    offline
     */
    suspend fun getDeviceSerialIds(androidSdkPath: String, port: Int = 5037): List<String> {
        val deviceSerialIds = mutableListOf<String>()
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P",  "$port", "devices")
        val result = executeCommand(array.toList())
        if (CODE_SUCCESS == result.code) {
            val lines = result.result
            lines?.let {
                it.forEach { line ->
                    if (line.endsWith("device")) {
                        //设置行
                        val index = line.indexOf("\t")
                        val id = line.substring(0, index)
                        deviceSerialIds.add(id)
                    }
                }
            }
        }
        return deviceSerialIds.toList()
    }

    /**
     * 获取设备的所有属性
     */
    suspend fun getDeviceProperties(androidSdkPath: String,
                            port: Int = 5037,
                            deviceSerialId: String,
                            ): Map<String, String> {
        val result = mutableMapOf<String, String>()
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId, "shell", "getprop")
        val cResult = executeCommand(array.toList())
        if (CODE_SUCCESS == cResult.code) {
            val lines = cResult.result
            lines?.let {
                it.forEach { line ->
                    val prop = parseProperty(line)
                    if (prop == null) {
                        logger.error("parse error: $line")
                    } else {
                        result.put(prop.first, prop.second)
                    }
                }
            }
        }
        return result.toMap()
    }

    /**
     * 解析属性
     */
    private fun parseProperty(line: String): Pair<String, String>? {
        val pattern = Regex("^\\[(.*?)\\]\\s*:\\s*\\[(.*?)\\]$")
        val result = pattern.matchEntire(line)
        if (result != null) {
            val key = result.groupValues[1]
            val value = result.groupValues[2]
            return Pair(key, value)
        } else {
            return null
        }
    }

    fun startServer(androidSdkPath: String, port: Int = 5037): Int {
        try {
            val process = ProcessBuilder()
                .command("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
                    "-P", "$port", "start-server")
                .redirectErrorStream(true)
                .start()
            process.inputReader().use {
                val line = it.readLine()
                if (line != null) {
                    println("startServer: -> $line")
                }
            }
            return process.waitFor()
        } catch (ignored: Exception) {
            ignored.printStackTrace()
            return -1
        }
    }

    /**
     * 格式要求: adb -P 5037 kill-server
     * 端口必须在命令前
     */
    suspend fun killServer(androidSdkPath: String, port: Int = 5037): CommandResult {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port", "kill-server")
        return executeCommand(array.toList())
    }

    /**
     * 宽高
     */
    suspend fun getWindowSize(androidSdkPath: String, port: Int = 5037, deviceSerialId: String): Pair<Int, Int>? {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "wm", "size")
        val cResult = executeCommand(array.toList())
        if (CODE_SUCCESS == cResult.code) {
            cResult.result?.let {
                it.forEach { line ->
                    if (line.startsWith("Physical size:")) {
                        return parseWindowSize(line)
                    }
                }
            }
        }
        return null
    }

    /**
     * 解析 adb 命令返回的屏幕分辨率字符串（如 "Physical size: 1080x2280"）
     * @param line 输入字符串（adb shell wm size 的输出结果）
     * @return Pair<Int, Int> ：左侧为宽度（width），右侧为高度（height）
     * @throws IllegalArgumentException 当输入格式不匹配时抛出
     */
    fun parseWindowSize(line: String): Pair<Int, Int>? {
        // 1. 正则表达式：匹配 "Physical size: " 开头，后续为 "数字x数字" 的格式
        // 分组说明：
        // - Group 1：宽度数字（如 1080）
        // - Group 2：高度数字（如 2280）
        val pattern = Regex("Physical size: (\\d+)x(\\d+)")
        // 2. 匹配输入字符串，提取分组
        val matchResult = pattern.matchEntire(line)
        // 3. 提取分组并转换为 Int（Group 0 是完整匹配结果，跳过）
        if (matchResult != null) {
            return  matchResult.groupValues[1].toInt() to matchResult.groupValues[2].toInt()
        }
        return null
    }

    /**
     * 滑动屏幕
     */
    suspend fun swipe(androidSdkPath: String, port: Int = 5037,
                      deviceSerialId: String,
                      startPoint: Pair<Int, Int>,
                      endPoint: Pair<Int, Int>,
                      duration: Int = 500): CommandResult {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId, "shell", "input", "swipe",
            "${startPoint.first}", "${startPoint.second}",
            "${endPoint.first}", "${endPoint.second}",
            "$duration")
        return executeCommand(array.toList())
    }

    suspend fun getDevices(androidSdkPath: String, port: Int = 5037): List<DeviceInfo> {
        val serialIds = getDeviceSerialIds(androidSdkPath, port)
        return serialIds.map {
            val props = getDeviceProperties(androidSdkPath, port,
                deviceSerialId = it)
            DeviceInfo(it, props)
        }.toList()
    }

    /**
     * 获取系统预置的应用包名
     * system: true表示获取系统预置的应用, false表示获取非系统预置的应用
     * enabled: true表示启用的应用, false表示禁用的应用
     * 在荣耀设备上需要指定用户: --user 0
     */
    suspend fun getDevicePkgList(androidSdkPath: String, port: Int = 5037,
                                       deviceSerialId: String,
                                                system: Boolean = true,
                                                enabled: Boolean = true): List<PackageInfo> {
        val optionSystemStr = if (system) {
            "-s"
        } else {
            "-3"
        }
        val optionEnabledStr = if (enabled) {
            "-e"
        } else {
            "-d"
        }
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId, "shell", "pm", "list", "packages",
            optionSystemStr, optionEnabledStr,
            "--user", "0")
        val cResult = executeCommand(array.toList())
        val list = mutableListOf<String>()
        if (CODE_SUCCESS == cResult.code) {
            cResult.result?.let {
                it.forEach { line ->
                    val pkgName = parsePkgName(line)
                    if (pkgName != null) {
                        list.add(pkgName)
                    }
                }
            }
        }
        return list.map {
            PackageInfo(it, system, enabled)
        }
    }

    /**
     * 解析包含包名的字符串，提取冒号后面的包名部分
     * @param packageString 格式如 "package:com.example.app" 的字符串
     * @return 提取的包名（如 "com.example.app"），若格式不正确则返回 null
     */
    fun parsePkgName(line: String): String? {
        // 检查字符串是否包含冒号
        val colonIndex = line.indexOf(':')
        // 确保冒号存在且后面有内容
        return if (colonIndex != -1 && colonIndex < line.length - 1) {
            // 截取冒号后面的部分作为包名
            line.substring(colonIndex + 1)
        } else {
            // 格式不正确，返回 null
            null
        }
    }


    suspend fun installApk(androidSdkPath: String, port: Int = 5037, deviceSerialId: String, apkPath: String): CommandResult {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId, "install", apkPath)
        return executeCommand(array.toList())
    }

    /**
     * 启用或者禁用应用
     * 使用荣耀X40 GT, 禁用应用失败, 暂时不开发此功能
     */
    suspend fun updatePkg(androidSdkPath: String, port: Int = 5037, deviceSerialId: String, pkgName: String,
                          enabled: Boolean): CommandResult {

        val str = if (enabled) {
            "enable"
        } else {
            "disable"
        }
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "pm", str, pkgName, "--user", "0")
        return executeCommand(array.toList())
    }

    /**
     * 清除数据
     */
    suspend fun deletePkgData(androidSdkPath: String, port: Int = 5037, deviceSerialId: String,
                              pkgName: String): CommandResult {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "pm", "clear", pkgName)
        return executeCommand(array.toList())
    }

    /**
     * 卸载应用
     */
    suspend fun uninstallPkg(androidSdkPath: String, port: Int = 5037, deviceSerialId: String,
                             pkgName: String): CommandResult {
        val array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "pm", "uninstall", pkgName)
        return executeCommand(array.toList())
    }

    const val DEVICE_TEMP_SCREENSHOT_PATH = "/sdcard/Pictures/Screenshots/adb_temp_screenshot.png"
    const val DEVICE_TEMP_SCREENSHOT_ELEMENTS_PATH = "/sdcard/Pictures/Screenshots/adb_temp_screenshot_elements.xml"

    /**
     * 获取截屏
     */
    suspend fun getScreenshot(androidSdkPath: String, port: Int = 5037, deviceSerialId: String): ImageBitmap? = coroutineScope {
        var array = arrayOf(
            "$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "screencap", "-p", DEVICE_TEMP_SCREENSHOT_PATH
        )
        var cResult = executeCommand(array.toList())
        if (CODE_SUCCESS != cResult.code) {
            logger.error(cResult.error?.joinToString("\n") ?: "unknown")
            return@coroutineScope null
        }
        val appDataDir = getAppDataDir()
        val localTempScreenshotPath = "$appDataDir/${deviceSerialId}_temp_screenshot.png"
        cResult = getFile(androidSdkPath, port, deviceSerialId, DEVICE_TEMP_SCREENSHOT_PATH, localTempScreenshotPath)
        if (CODE_SUCCESS != cResult.code) {
            logger.error(cResult.error?.joinToString("\n") ?: "unknown")
            return@coroutineScope null
        }
        //删除设备上的临时截屏图片
        removeFile(androidSdkPath, port, deviceSerialId, DEVICE_TEMP_SCREENSHOT_PATH)
        val resultDeferred = async(Dispatchers.IO) {
            FileInputStream(File(localTempScreenshotPath)).use {
                it.readBytes()
            }
        }
        val result = resultDeferred.await()
        return@coroutineScope Image.makeFromEncoded(result).toComposeImageBitmap()
    }

    /**
     * 删除文件
     */
    suspend fun removeFile(androidSdkPath: String, port: Int = 5037, deviceSerialId: String, filePath: String): CommandResult {
        var array = arrayOf("$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "rm", filePath)
        return executeCommand(array.toList())
    }

    /**
     * 获取截屏的代码
     */
    suspend fun getScreenshotElements(androidSdkPath: String, port: Int = 5037, deviceSerialId: String): UiHierarchy? {
        var array = arrayOf(
            "$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "uiautomator", "dump", DEVICE_TEMP_SCREENSHOT_ELEMENTS_PATH
        )
        var cResult = executeCommand(array.toList())
        if (CODE_SUCCESS != cResult.code) {
            logger.error(cResult.error?.joinToString("\n") ?: "unknown")
            return null
        }
        val appDataDir = getAppDataDir()
        val localTempScreenshotPath = "$appDataDir/${deviceSerialId}_temp_screenshot_elements.xml"
        cResult = getFile(androidSdkPath, port, deviceSerialId, DEVICE_TEMP_SCREENSHOT_ELEMENTS_PATH, localTempScreenshotPath)
        if (CODE_SUCCESS != cResult.code) {
            logger.error(cResult.error?.joinToString("\n") ?: "unknown")
            return null
        }
        //解析数据
        val xmlMapper = XmlMapper().registerKotlinModule()
        val uiHierarchy = FileInputStream(localTempScreenshotPath).use {
            xmlMapper.readValue<UiHierarchy>(it, UiHierarchy::class.java)
        }
        return uiHierarchy
    }

    /**
     * 获取设备上的文件, 将设备上的文件拷贝到电脑上
     * remotePath: 表示设备上的文件路径
     * localPath: 表示电脑上的文件路径
     */
    suspend fun getFile(androidSdkPath: String, port: Int = 5037, deviceSerialId: String, remotePath: String, localPath: String): CommandResult {
        val array = arrayOf(
            "$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "pull", remotePath, localPath
        )
        return executeCommand(array.toList())
    }

    /**
     * 点击屏幕的指定位置
     *
     */
    suspend fun tap(androidSdkPath: String, port: Int = 5037,
                    deviceSerialId: String, point: Pair<Int, Int>): CommandResult {
        val array = arrayOf(
            "$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "input", "tap", "${point.first}", "${point.second}"
        )
        return executeCommand(array.toList())
    }


    /**
     * 点击屏幕的指定位置
     *
     */
    suspend fun startApp(androidSdkPath: String, port: Int = 5037,
                         deviceSerialId: String, pkgName: String, activityName: String): CommandResult {
        val array = arrayOf(
            "$androidSdkPath/$PACKAGE_PLATFORM_TOOLS/${getAdbName()}",
            "-P", "$port",
            "-s", deviceSerialId,
            "shell", "am", "start",
            "-a", "android.intent.action.MAIN",
            "-c", "android.intent.category.LAUNCHER",
            "-n", "${pkgName}/${activityName}"
        )
        return executeCommand(array.toList())
    }


    suspend fun findNodeBy(hierarchy: UiHierarchy, text: String): UiNode? {
        hierarchy.notes.forEach {
            val item = findNodeBy(it, text)
            if (item != null) {
                return item;
            }
        }
        return null
    }

    /**
     * 根据文本找节点
     */
    suspend fun findNodeBy(node: UiNode, text: String): UiNode? {
        if (node.text == text) {
            //当前节点满足
            return node
        }
        node.children.forEach {
            val item = findNodeBy(it, text)
            if (item != null) {
                return item
            }
        }
        return null
    }

}